package com.ynqxg.router.view

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.ynqxg.router.R
import kotlin.math.min

class SmartTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    data class Element(
        var value: Any,
        val x: Float,
        val y: Float
    )

    companion object {
        private val iconMap = mutableMapOf<String, Bitmap?>()

        fun addIcon(key: String, icon: Bitmap?) {
            iconMap[key] = icon
        }
    }

    // 默认字体大小
    private val defaultSize = dp2px(16f)
    private val defaultLineSpace = dp2px(2f)

    // 默认字体颜色
    private val defaultColor = Color.parseColor("#333333")

    // 替换图标的文本规则
    private val iconRegex = "\\*\\{[a-zA-Z1-9]+\\}".toRegex()

    private var drawLines = mutableListOf<Element>()
    private var lineCount = 0
    private val textPaint = TextPaint()
    private var text: String
    private var previewText: String
    private var textSize: Float
    private var textColor: Int
    private var maxLine: Int
    private var lineSpace: Float
    private var baseLine = 0
    private var startTime = System.currentTimeMillis()

    init {
        // 读取xml获得的参数
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.SmartTextView)
        text = attributes.getString(R.styleable.SmartTextView_android_text) ?: ""
        textSize = attributes.getDimension(R.styleable.SmartTextView_android_textSize, defaultSize)
        lineSpace =
            dp2px(attributes.getDimension(R.styleable.SmartTextView_lineSpace, defaultLineSpace))
        textColor = attributes.getColor(R.styleable.SmartTextView_android_textColor, defaultColor)
        maxLine = attributes.getInteger(R.styleable.SmartTextView_android_maxLines, 0)
        attributes.recycle()
        previewText = text
        // 初始化画笔属性
        textPaint.isAntiAlias = true
        textPaint.textSize = textSize
        textPaint.color = textColor
        val metrics = textPaint.fontMetricsInt
        // 计算文字基线 这个2dp是因为肉眼感觉位置太低而添加，不知道为什么
        baseLine = metrics.descent.minus(metrics.ascent).div(2).minus(metrics.descent)
            .plus(dp2px(2f).toInt())
        iconMap.forEach {
            val bitmap = it.value
            if (bitmap != null) {
                iconMap[it.key] = Bitmap.createScaledBitmap(
                    bitmap,
                    textSize.toInt(),
                    textSize.toInt(),
                    false
                )
            }
        }
    }

    private fun dp2px(sp: Float): Float {
        return context.resources.displayMetrics.density.times(sp)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // 测量宽度
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        var widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val paddingH = paddingStart.plus(paddingEnd)
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = min(textPaint.measureText(previewText).plus(paddingH).toInt(), widthSize)
        }
        // 通过测量后的宽度，计算行数，方便后续高度测量
        val safeWidth = widthSize.minus(paddingH)
        measureLines(safeWidth)
        // 测量高度
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        var heightSize = MeasureSpec.getSize(heightMeasureSpec)
        val paddingV = paddingTop.plus(paddingBottom)
        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = min(
                textSize.plus(lineSpace).times(lineCount.plus(1))
                    .plus(paddingV).plus(baseLine).minus(lineSpace)
                    .toInt(),
                heightSize
            )
        }
        setMeasuredDimension(widthSize, heightSize)
    }

    /**
     * 测算文字全部展示需要的行数
     */
    private fun measureLines(widthSize: Int, mustMeasure: Boolean = false) {
        if (drawLines.isEmpty() || mustMeasure) {
            // 重置已测量过的数据
            lineCount = 0
            drawLines.clear()
            // 开始测量行数
            startMeasureLines(previewText, widthSize)
        }
    }

    /**
     * 测量行数
     * @param beforeText 需要测量的字符串
     * @param widthSize view的宽度
     */
    private fun startMeasureLines(beforeText: String, widthSize: Int) {
        if (maxLine != 0 && maxLine < lineCount) {
            lineCount = maxLine
            return
        }
        // 重置x坐标
        var eX = 0f
        // 计算当前行y坐标
        var eY = measureEy()
        var realBeforeText = beforeText
        // 如果行首是上次分行时遗留的换行符，则去除首个换行符
        if (realBeforeText.startsWith("\n")) {
            realBeforeText = realBeforeText.replaceFirst("\n", "")
        }
        // 获取下一个换行符在字符串中的索引值
        val nextLineIndex = realBeforeText.indexOf("\n")
        // 获取当前行的文字 如果还有换行符就截取换行前的文字，如果没有就获得整段文字
        val currentLine = if (nextLineIndex > 0) {
            realBeforeText.substring(0, nextLineIndex)
        } else {
            realBeforeText
        }
        // 获取当前行需要替换的图标列表
        val iconList = getIcon(currentLine)
        if (iconList.isNotEmpty()) {
            // 上次结束的字符索引
            var lastCount = 0
            iconList.forEach { iconElement ->
                // 处理图标前的文字
                if (lastCount < iconElement.start) {
                    // 截取图标前面的文字
                    val substring = currentLine.substring(0, iconElement.start)
                    // 将文字转为字符列表，方便后面测量文字
                    val charList = substring.split("")
                    charList.forEach { char ->
                        // 添加文字到绘制列表
                        addDrawItem(Element(char, eX, eY))
                        // 累加x轴位移量【当前字符的宽度】
                        eX += measureText(char)
                        // 判断是否超出边界，超出则换行
                        if (eX.plus(textSize) > widthSize) {
                            lineCount++
                            eY = measureEy()
                            eX = 0f
                        }
                    }
                }
                // 判断是否超出边界，超出则换行
                if (eX.plus(textSize) > widthSize) {
                    lineCount++
                    eY = measureEy()
                    eX = 0f
                }
                // 获取需要转换的icon对应的字符
                val iconStr = realBeforeText.substring(iconElement.start, iconElement.end)
                iconMap[iconStr]?.also { icon ->
                    // 计算y坐标便宜 因为eY计算的是文字坐标，所以需要减去半个文字高度和文字基线高度【文字坐标按中间
                    // 位置绘制，图片按顶部位置绘制】 这里加2dp是因为计算文字基线时添加的2dp 【这个2dp是因为肉眼感
                    // 觉位置太低而添加，不知道为什么】
                    val offsetY = eY.minus(textSize.div(2))
                        .minus((baseLine).minus(dp2px(2f)))
                    // 添加字符到绘制列表
                    addDrawItem(Element(icon, eX, offsetY))
                    // 累加x轴位移量【一个文字宽度】
                    eX += textSize
                }
                // 本次循环结束，更新索引
                lastCount = iconElement.end
            }
            // 处理最后一个图标后面的文字 【同段前文字处理】
            val substring = currentLine.substring(lastCount)
            val charList = substring.split("")
            charList.forEach { char ->
                addDrawItem(Element(char, eX, eY))
                eX += measureText(char)
                if (eX.plus(textSize) > widthSize) {
                    lineCount++
                    eY = measureEy()
                    eX = 0f
                }
            }
        } else {
            // 无图标情况下文字渲染 【同有图标情况下段前处理】
            val charList = currentLine.split("")
            charList.forEach { char ->
                addDrawItem(Element(char, eX, eY))
                eX += measureText(char)
                if (eX.plus(textSize) > widthSize) {
                    lineCount++
                    eY = measureEy()
                    eX = 0f
                }
            }
        }
        // 判断是否还有下一行
        if (nextLineIndex > 0 && nextLineIndex < beforeText.length) {
            lineCount++
            // 截取下一行并递归
            startMeasureLines(beforeText.substring(nextLineIndex.plus(1)), widthSize)
        }
    }
    /**
     * 添加要绘制的项到绘制列表
     * @param item 要绘制的项
     */
    private fun addDrawItem(item: Element) {
        if (maxLine == 0 || lineCount < maxLine) {
            // 如果最后一个是文字并且在同一行就加到最后一行，否则新添加一个
            val lastElement = drawLines.lastOrNull()
            if (lastElement != null && lastElement.value is String && item.value is String) {
                if (lastElement.y == item.y) {
                    lastElement.value = "${lastElement.value}${item.value}"
                } else {
                    drawLines.add(item)
                }
            } else {
                drawLines.add(item)
            }
//            drawLines.add(item)
        }
    }

    /**
     * 测量文字宽度
     * @param str 要测量的文字
     * @return 文字宽度
     */
    private fun measureText(str: String): Float {
        return textPaint.measureText(str)
    }

    /**
     * 测量当前行高度
     * @return 当前行高度
     */
    private fun measureEy(): Float {
        return lineCount.times(textSize)
            .plus(lineSpace.times(lineCount))
            .plus(baseLine).plus(textSize.div(2))
    }

    /**
     * 获取字符串中需要替换的图标列表
     * @param text 要检测的字符串
     * @param offset 偏移量
     * @return 图标字符串在整体字符串中的前后索引的列表
     */
    private fun getIcon(text: String, offset: Int = 0): List<IconStrRange> {
        val list = mutableListOf<IconStrRange>()
        // 查找到匹配格式的第一个字符
        val element = iconRegex.find(text)?.value ?: return list
        // 匹配到的字符开始和结束的索引
        val startIndex = offset + text.indexOf(element)
        val endIndex = startIndex + element.length
        // 添加到列表
        list.add(IconStrRange(startIndex, endIndex))
        // 根据后索引截取下次要获取的字符串
        val afterStr = text.substring(endIndex.minus(offset))
        if (afterStr.isNotEmpty()) {
            // 递归查询下一个字符并加入到列表
            list.addAll(getIcon(afterStr, endIndex))
        }
        return list
    }

    data class IconStrRange(
        val start: Int,
        val end: Int
    )

    /**
     * 是否含有特殊字符 用于判断上一行是否需要退一位
     * TODO:下次再加入判断吧
     * @param char 要判断的字符串 用起始字符判断
     */
    private fun hasSymbol(char: String): Boolean {
        return symbolList.find {
            char.startsWith(it)
        }?.isNotEmpty() == true
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.also { c ->
            drawLines.forEach { line ->
                if (line.value is String) {
                    c.drawText(line.value as String, line.x, line.y, textPaint)
                }
                if (line.value is Bitmap) {
                    c.drawBitmap(line.value as Bitmap, line.x, line.y, textPaint)
                }
            }
            val timeMillis = System.currentTimeMillis()
            Log.d("SmartTextView.kt", "onDraw: 渲染用时${timeMillis.minus(startTime)}ms")
        }
    }


    // 常用符号 行首字符为下列字符则上一行退一位
    private val symbolList = listOf(
        ",",
        ".",
        "/",
        ";",
        "'",
        "[",
        "]",
        "\\",
        "<",
        ">",
        "?",
        ":",
        "\"",
        "{",
        "}",
        "|",
        "`",
        "~",
        "!",
        "@",
        "#",
        "$",
        "%",
        "^",
        "&",
        "*",
        "(",
        ")",
        "-",
        "_",
        "=",
        "+",
        "，",
        "。",
        "、",
        "；",
        "‘",
        "【",
        "】",
        "、",
        "·",
        "《",
        "》",
        "？",
        "：",
        "“",
        "”",
        "{",
        "}",
        "|",
        "~",
        "！",
        "@",
        "#",
        "￥",
        "%",
        "……",
        "&",
        "*",
        "（",
        "）",
        "-",
        "——",
        "=",
        "+",
    )
}